/*
Derived HTML.
Copyright (C) 2009 dhudson@pushtechnology.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/
package org.dhtml;

import java.io.File;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.Remark;
import org.htmlparser.nodes.RemarkNode;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.tags.BodyTag;
import org.htmlparser.tags.HeadTag;
import org.htmlparser.tags.Html;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;

public class DHTMLNodeParser {

    private File theFile;
    private Parser theParser;
    private NodeList theNodeList;

    private static final Pattern FILE_PATTERN =
        Pattern.compile("file=\"([^>]+?)\"");

    private boolean isExtended = false;
    private HashMap<String,TagNode> theNodeIDs;

    private TagNode theHeadNode;
    private TagNode theHtmlNode;
    private TagNode theBodyNode;

    /**
     * Constructor
     * 
     * @param file
     * @throws APIException
     * @throws ParserException
     */
    DHTMLNodeParser(File file) throws DHTMLException, ParserException {
        theFile = file;
        theParser = new Parser();
        theParser.setInputHTML(Utils.readFileAsString(file));
        theNodeList = new NodeList();
        theNodeIDs = new HashMap<String,TagNode>();

        for (NodeIterator i = theParser.elements();i.hasMoreNodes();) {
            Node node = i.nextNode();
            processNodes(node);
            if (!isExtended) {
                theNodeList.add(node);
            }
        }
    }

    /**
     * getNodeList
     * 
     * @return
     */
    NodeList getNodeList() {
        return theNodeList;
    }

    /**
     * getIDMap
     * 
     * @return
     */
    HashMap<String,TagNode> getIDMap() {
        return theNodeIDs;
    }

    /**
     * processNodes
     * 
     * @param node
     * @throws APIException
     * @throws ParserException
     */
    void processNodes(Node node) throws DHTMLException, ParserException {

        if (node instanceof RemarkNode) {
            RemarkNode remark = (RemarkNode)node;
            String comment = remark.getText();
            if (comment.startsWith("@DInclude")) {
                Matcher fileMatcher = FILE_PATTERN.matcher(comment);
                if (fileMatcher.find()) {
                    // parse for the include file...
                    File includeFile =
                        new File(theFile.getParent(),fileMatcher.group(1));
                    if (includeFile.exists()&&includeFile.canRead()) {
                        processIncludeFile(remark,includeFile);
                    }
                    else {
                        System.out.println("Can't process include file "
                            +includeFile.getAbsolutePath());
                    }
                }
            }
            else if (comment.startsWith("@DInherit")) {
                Matcher fileMatcher = FILE_PATTERN.matcher(comment);
                if (fileMatcher.find()) {
                    File extendFile =
                        new File(theFile.getParent(),fileMatcher.group(1));
                    if (extendFile.exists()&&extendFile.canRead()) {
                        processExtendFile(remark,extendFile);
                    }
                }
            }
        }
        else if (node instanceof TagNode) {
            TagNode tagNode = (TagNode)node;
            String id = tagNode.getAttribute("id");

            if (isExtended) {
                System.out.println("We are extended and I have got..."
                    +tagNode.getRawTagName());

                Node parent = node.getParent();
                if (parent!=null) {
                    if (parent instanceof HeadTag) {
                        theHeadNode.getChildren().add(tagNode);
                    }
                    if (parent instanceof BodyTag) {
                            if (theNodeIDs.containsKey(id)) {
                                // This is the old ID
                                TagNode extendedNode = theNodeIDs.get(id);
                                extendedNode.setChildren(tagNode.getChildren());
                                
                                //REMOVE:
                                System.out.println("Extended ID .."+id);
                            }
                            else {
                                theBodyNode.getChildren().add(tagNode);
                                theNodeIDs.put(id,tagNode);
                            }
                    }
                }
            }
            else {
                if (id!=null) {
                    theNodeIDs.put(id,tagNode);
                }
            }

            // process recursively (nodes within nodes) via getChildren()
            NodeList nl = node.getChildren();
            if (null!=nl) {
                for (NodeIterator i = nl.elements();i.hasMoreNodes();) {
                    processNodes(i.nextNode());
                }
            }
        }
    }

    /**
     * processIncludeFile
     * 
     * @param remark
     * @param includeFile
     * @throws APIException
     * @throws ParserException
     */
    private void processIncludeFile(Remark remark,File includeFile)
    throws DHTMLException,
    ParserException {
        //REMOVE:
        System.out.println("I got a include... "+includeFile.getAbsolutePath());

        // Just to mess with your head, this will recursively parse as well
        DHTMLNodeParser dhtmlParser = new DHTMLNodeParser(includeFile);

        Node parent = remark.getParent();
        NodeList oldList = parent.getChildren();
        NodeList newNodeList = new NodeList();

        int index = oldList.indexOf(remark);
        // Copy up to the include...
        for (int i = 0;i<index;i++) {
            newNodeList.add(oldList.elementAt(i));
        }

        // Now copy in the new nodes...
        NodeList extra = dhtmlParser.getNodeList();
        for (int i = 0;i<extra.size();i++) {
            newNodeList.add(extra.elementAt(i));
        }

        // Now copy the rest of the nodes
        for (int i = index+1;i<oldList.size();i++) {
            newNodeList.add(oldList.elementAt(i));
        }

        parent.setChildren(newNodeList);
    }

    /**
     * processExtendFile
     * 
     * @param remark
     * @param extendFile
     * @throws APIException
     * @throws ParserException
     */
    private void processExtendFile(Remark remark,File extendFile)
    throws DHTMLException,
    ParserException {
        System.out.println("I got a extend... "+extendFile.getAbsolutePath());

        DHTMLNodeParser dhtmlParser = new DHTMLNodeParser(extendFile);
        NodeList nodes = dhtmlParser.getNodeList();

        if (theNodeList.size()>0) {
            // ASSERT...
            System.out
                .println("This isn't right...., unless we are extending an extend");
        }

        isExtended = true;

        theNodeList.add(nodes);

        // Need to get all of the ID's as well.
        theNodeIDs = new HashMap<String,TagNode>(dhtmlParser.getIDMap());

        for (int i = 0;i<theNodeList.size();i++) {
            Node node = theNodeList.elementAt(i);
            if (node instanceof Html) {
                theHtmlNode = (TagNode)node;
            }
        }

        // now looking for Head and Body..
        for (int j = 0;j<theHtmlNode.getChildren().size();j++) {
            Node subNode = theHtmlNode.getChildren().elementAt(j);
            if (subNode instanceof HeadTag) {
                theHeadNode = (TagNode)subNode;
            }
            else if (subNode instanceof BodyTag) {
                theBodyNode = (TagNode)subNode;
            }
        }
    }
}
